#include "communicator.h"
#include "workbench.h"
#include "scriptapplication.h"
#include "settings.h"
#include "datatreehelper.h"
#include "message.h"
#include "defines.h"
#include "databasemanager.h"
#include "unzip.h"
#include "errorreporter.h"

#include <QProgressDialog>
#include <QTemporaryFile>
#include <QXmlStreamWriter>
#include <QMessageBox>
#include <QSqlDatabase>
#include <QDebug>
#include <QDir>

Communicator* Communicator::m_instance = 0;

static void showUserMessage(const QString& text, const QString& title = QString(), QMessageBox::Icon icon = QMessageBox::Information)
{
    QMessageBox box(icon, title, text);
    box.setWindowModality(Qt::WindowModal);
#ifdef Q_OS_ANDROID
    box.setWindowFlags(Qt::ToolTip);
    box.setStyleSheet("QDialog{ background-color: rgb(235, 242, 255);border: 1px solid black; border-style: solid; border-radius: 4px; }");
#endif
    box.exec();
}

Communicator::Communicator() : QObject()
{
    m_project = Workbench::instance()->project();
    m_app = static_cast<ScriptApplication*>(ScriptApplication::instance());
    m_settings = Settings::instance();
}

Communicator::~Communicator()
{
}

Communicator *Communicator::instance()
{
    if(m_instance == 0)
        m_instance = new Communicator();

    return m_instance;
}

bool Communicator::sendTableData(DataTreeHelper *dataTree, int servNo)
{
    bool res = true;
    QString server;
    int port;
    Message msg;
    msg.setParam("COM",m_project->property(XML_PROJECTNAME).toString());
    msg.setParam("KP", m_app->getUser());
    msg.setParam("SERV",QString::number(servNo));

    QStringList tablesList = dataTree->tablesStringList();

    if( servNo >= m_settings->serversCount() ) {        
        showUserMessage("Проверте настройку серверов", "Ошибка", QMessageBox::Critical);
        return false;
    }

    server = m_settings->serverHost(servNo);
    port =  m_settings->serverPort(servNo);
    QProgressDialog progress("Подготовка данных", "Отмена",0,0);
    progress.setWindowTitle("Обмен данными");
    progress.show();
    QCoreApplication::processEvents(QEventLoop::AllEvents, 80);

    QTemporaryFile *file = msg.getBodyFile();
    file->setAutoRemove(false);
    file->open();

    QXmlStreamWriter stream(file);
    stream.setAutoFormatting(true);
    stream.writeStartDocument();
    stream.writeStartElement(XML__UFDATA);
    writeXmlHead(&stream, "SendData", tablesList );

    //**Write tables**
    stream.writeStartElement(XML__DATA);
    QList<DataTreeHelper*> dataTreeList = dataTree->dataTreeList();
    for(int i = 0; i < dataTreeList.size(); i++)
        DatabaseManager::tableToXml(&stream, dataTreeList[i]);
    stream.writeEndElement();// XML__DATA
    //***************

    stream.writeEndElement();// XML__UFDATA
    stream.writeEndDocument();
    file->close();

    if(!msg.packBody()) {
        showUserMessage("Не удалось заархивировать файл", "Ошибка", QMessageBox::Critical);
        return false;
    }

    Messenger messenger(server,port, &progress);
    progress.setLabelText("Подключение к серверу");
    QCoreApplication::processEvents(QEventLoop::AllEvents, 90);
    Messenger::MessengerError err = messenger.sendMessage(&msg);
    if(err == Messenger::NoError ) {
        QString servRetMessage = messenger.lastMessage()->getParam("MSG");
        int ret = messenger.lastMessage()->getParam("RET").toInt();
        if(servRetMessage.length() > 2 && ret == 0)
            QMessageBox::information(0,"", servRetMessage);
        else if(ret != 0)
            QMessageBox::critical(0,"Ошибка:"+QString::number(ret), servRetMessage);

        if(ret != 0)
            res = false;
    } else {
        progress.close();
        if( err == Messenger::NoConnection ) {
            reportError("No server connection");
            showUserMessage("Нет соединения с сервером", "Ошибка", QMessageBox::Critical);
        } else if( err == Messenger::ConnectionLost ) {
            reportError("Server connection lost");
            showUserMessage("Соединение потеряно", "Ошибка", QMessageBox::Critical);
        }
        res = false;
    }

    return res;
}

bool Communicator::sendDoc(DataTreeHelper *dataTree, int servNo)
{
    bool res = true;
    QString server;
    int port;
    Message msg;
    msg.setParam("COM",m_project->property(XML_PROJECTNAME).toString());
    msg.setParam("KP", m_app->getUser());
    msg.setParam("SERV",QString::number(servNo));

    if( servNo >= m_settings->serversCount() ) {
        showUserMessage("Проверте настройку серверов", "Ошибка", QMessageBox::Critical);
        return false;
    }

    server = m_settings->serverHost(servNo);
    port =  m_settings->serverPort(servNo);
    QProgressDialog progress("Подготовка данных", "Отмена",0,0);
#ifdef Q_OS_ANDROID
    progress.setWindowFlags(Qt::ToolTip);
    progress.setStyleSheet("QDialog{ background-color: rgb(235, 242, 255);border: 1px solid black; border-style: solid; border-radius: 4px; }");
#endif

    progress.setWindowTitle("Обмен данными");
    progress.show();
    QCoreApplication::processEvents(QEventLoop::AllEvents, 80);

    QTemporaryFile *file = msg.getBodyFile();
    file->setAutoRemove(false);
    file->open();

    QXmlStreamWriter stream(file);
    stream.setAutoFormatting(true);
    //stream.writeStartDocument();
    stream.writeStartElement(XML__UFDATA);
    writeXmlHead(&stream, "SendDoc", QStringList()<<dataTree->getTableName() );

    //**Write tables**
    stream.writeStartElement(XML__DATA);
    res = DatabaseManager::dataTreeToXml(&stream, dataTree);
    stream.writeEndElement();// XML__DATA
    //***************

    stream.writeEndElement();// XML__UFDATA
    stream.writeEndDocument();
    file->close();

    if(!msg.packBody()) {
        showUserMessage("Не удалось заархивировать файл", "Ошибка", QMessageBox::Critical);
        return false;
    }

    if(!res) return false;

    Messenger messenger(server,port, &progress);
    progress.setLabelText("Подключение к серверу");
    QCoreApplication::processEvents(QEventLoop::AllEvents, 90);
    Messenger::MessengerError err = messenger.sendMessage(&msg);
    if(err == Messenger::NoError ) {
        QString servRetMessage = messenger.lastMessage()->getParam("MSG");
        int ret = messenger.lastMessage()->getParam("RET").toInt();
        if(servRetMessage.length() > 2 && ret == 0)
            QMessageBox::information(0,"", servRetMessage);
        else if(ret != 0)
            QMessageBox::critical(0,"Ошибка:"+QString::number(ret), servRetMessage);

        if(ret != 0)
            res = false;
    } else {
        progress.close();
        if( err == Messenger::NoConnection )
            QMessageBox::critical(0,"Ошибка","Нет соединения с сервером");
        else if( err == Messenger::ConnectionLost ) {
            QMessageBox::critical(0,"Ошибка","Соединение потеряно");
        }
        res = false;
    }

    return res;
}

bool Communicator::sendMessage(const QString &params, int servNo)
{
    bool res = true;
    res = sendCommand("SendMsg", servNo, params);
    return res;
}

QString Communicator::getRemoteInfo(const QString &params, int servNo)
{       
    if( sendCommand("GetInfo", servNo, params) ) {
        Message *message = m_messenger.lastMessage();
        UnZip::ErrorCode ec;
        UnZip uz;

        ec = uz.openArchive(message->getBodyFile());
        if (ec != UnZip::Ok) {
            qDebug() << "Failed to open archive: " << uz.formatError(ec).toAscii().data();
            return QString();
        }

        QString extractPath = QDir::tempPath();
        ec = uz.extractAll(extractPath);
        if (ec != UnZip::Ok) {
            qDebug() << "Extraction failed: " << uz.formatError(ec).toAscii().data();

            uz.closeArchive();
            return QString();
        }
        QFile file(extractPath+"/ServTmp.xml");
        file.open(QFile::ReadOnly);
        QByteArray ba = file.readAll();
        return ba;
    }
    return QString();
}

bool Communicator::getRemoteFile(const QString &name, int servNo)
{
    bool res = true;
    res = sendCommand("GetFile", servNo, "FileName="+name, QStringList());
    if(res)
        res = processGetFile(m_messenger.lastMessage());
    return res;
}

bool Communicator::getRemoteTable(const QString &tablesName, const QString &vars, int servNo, const QString &dbName)
{
    bool res = true;
    QStringList tables = tablesName.split(';');
    res = sendCommand("GetSpr", servNo, vars, tables);
    if(res)
        res = processGetRemoteTable(m_messenger.lastMessage(), dbName);
    return res;
}

bool Communicator::getRemoteDoc(const QString &docName, const QString &vars, const QString &deleteFilter, int servNo, const QString &dbName)
{
    bool res = true;
    res = sendCommand("GetDoc", servNo, vars, docName.split(';'));
    if(res)
        res = processGetRemoteTable(m_messenger.lastMessage(),dbName, deleteFilter);
    return res;
}

int Communicator::serversCount()
{
    return m_settings->serversList().size();
}

QStringList Communicator::serversList()
{
    return m_settings->serversList();
}

void Communicator::writeXmlHead(QXmlStreamWriter *pXml, const QString &command, const QStringList &tablesList, const QString &variables)
{
    QStringList varsList;
    if(variables.length()>0)
        varsList = variables.split(';');

    pXml->writeStartElement(XML__COMMAND);
        pXml->writeTextElement(XML__VERSION, QApplication::applicationVersion() +"."+ m_project->property(XML_VERSION).toString());
        pXml->writeTextElement(XML__OS, static_cast<ScriptApplication*>(QApplication::instance())->os());
        pXml->writeTextElement(XML__KOMMAND, command);
        pXml->writeTextElement(XML__USER, m_app->getUser());
        pXml->writeTextElement(XML__PASSWORD, m_app->getPassword());

        pXml->writeStartElement(XML__PARAMS);
        if(varsList.size()>0) {
            pXml->writeStartElement(XML__VARS);
            for(int i = 0; i < varsList.size(); i++) {
                QStringList var = varsList.at(i).split('=');
                if(var.size()==2)
                    pXml->writeTextElement(var.at(0),var.at(1));
            }
            pXml->writeEndElement();
        }        
        if(tablesList.size()>0) {            
            if(command.compare("GetDoc") == 0 || command.compare("SendDoc") == 0)
                pXml->writeStartElement(XML__DOCSL);
            else
                pXml->writeStartElement(XML__TABLES);
            pXml->writeAttribute("type","array");
            pXml->writeAttribute("size",QString::number(tablesList.size()));

            for(int i = 0; i < tablesList.size(); i++ )
                pXml->writeTextElement(XML__TABLE_NAME, tablesList[i]);

            pXml->writeEndElement(); // XML__TABLES
        }
        pXml->writeEndElement(); // XML__PARAMS

        pXml->writeEndElement();
}

bool Communicator::sendCommand(const QString &command, int servNo, const QString &variables, const QStringList &tables)
{
    bool res = true;
    QString server;
    int port;
    Message msg;
    msg.setParam("COM",m_project->property(XML_PROJECTNAME).toString());
    msg.setParam("KP", m_app->getUser());
    msg.setParam("SERV",QString::number(servNo));

    if( servNo >= m_settings->serversCount() ) {
        QMessageBox::critical(0,"Ошибка", "Проверте настройку серверов",0,0);
        return false;
    }

    server = m_settings->serverHost(servNo);
    port =  m_settings->serverPort(servNo);
    QProgressDialog progress("Подготовка данных", "Отмена",0,0);
#ifdef Q_OS_ANDROID
    progress.setWindowFlags(Qt::ToolTip);
    progress.setStyleSheet("QDialog{ background-color: rgb(235, 242, 255);border: 1px solid black; border-style: solid; border-radius: 4px; }");
#endif
    progress.setWindowTitle("Обмен данными");
    progress.show();
    QCoreApplication::processEvents(QEventLoop::AllEvents, 80);

    QTemporaryFile *file = msg.getBodyFile();
    file->setAutoRemove(false);
    file->open();

    QXmlStreamWriter stream(file);
    stream.setAutoFormatting(true);
    //stream.writeStartDocument();
                                                                 //stream.writeStartElement("GrandaLiteXML");
    stream.writeStartElement(XML__UFDATA);
    writeXmlHead(&stream, command, tables, variables );
    stream.writeEndElement();// XML__UFDATA
                                                                 //stream.writeEndElement();
    stream.writeEndDocument();
    file->close();
    progress.setLabelText("Подключение к серверу");
    QCoreApplication::processEvents(QEventLoop::AllEvents, 90);
    if(!msg.packBody()) {        
        showUserMessage("Не удалось заархивировать файл", "Ошибка", QMessageBox::Critical);
        return false;
    }
    Messenger::MessengerError err = m_messenger.sendMessage(&msg, server, port, &progress);
    if(err == Messenger::NoError ) {
        QString servRetMessage = m_messenger.lastMessage()->getParam("MSG");
        int ret = m_messenger.lastMessage()->getParam("RET").toInt();
        if(servRetMessage.length() > 5 && ret == 0)
            showUserMessage(servRetMessage);
        else if(ret != 0) {
            reportError(servRetMessage);
            showUserMessage( servRetMessage, "Ошибка:"+QString::number(ret), QMessageBox::Critical);
            res = false;
        }
    } else {
        progress.close();
        if( err == Messenger::NoConnection ) {
            reportError("No server connection");
            showUserMessage("Нет соединения с сервером", "Ошибка", QMessageBox::Critical);            
        }
        else if( err == Messenger::ConnectionLost ) {
            reportError("Server connection lost");
            showUserMessage("Соединение потеряно", "Ошибка", QMessageBox::Critical);            
        }

        res = false;
    }

    return res;
}

void Communicator::processMessage(const QString &command, Message *message)
{
    Q_ASSERT(message);
    if( command.compare("GetFile") == 0 )
        processGetFile(message);
}

bool Communicator::processGetFile(Message *message)
{
    UnZip::ErrorCode ec;
    UnZip uz;

    ec = uz.openArchive(message->getBodyFile());
    if (ec != UnZip::Ok) {        
        reportError("Failed to open archive");
        return false;
    }

    ec = uz.extractAll(message->getParam("PARX"));
    if (ec != UnZip::Ok) {
        reportError("Zip extraction failed");
        uz.closeArchive();
        return false;
    }
    return true;
}

bool Communicator::processGetRemoteTable(Message *message, const QString &dbName, const QString &deleteFilter)
{
    bool res;
    QSqlDatabase db;
    if(dbName.length()>0)
        db = QSqlDatabase::database(dbName);
    else
        db = QSqlDatabase::database();
    message->debug();
    if( message->getParam("ZIP").toInt() == 2) {
        UnZip::ErrorCode ec;
        UnZip uz;
        ec = uz.openArchive(message->getBodyFile());
        if (ec != UnZip::Ok) {            
            reportError("Failed to open archive");
            return false;
        }
        QString extractPath = QDir::tempPath() + "/.scripter";
        QDir dir(extractPath);
        if(!dir.exists())
            dir.mkdir(extractPath);
        ec = uz.extractAll(extractPath);
        if (ec != UnZip::Ok) {
            reportError("Zip extraction failed");
            uz.closeArchive();
            return false;
        }
        QTemporaryFile file;
        file.setFileName(extractPath + "/ServTmp.xml");
        res = DatabaseManager::instance()->updateTables(&file, db, deleteFilter);

    } else
        res = DatabaseManager::instance()->updateTables(message->getBodyFile(), db, deleteFilter);

    return res;
}

void Communicator::reportError(const QString &errMsg)
{
    eReporter()<<"proj="<<m_project->property(XML_PROJECTNAME)<<";user="<<m_app->getUser()<<";err="<<errMsg;
}


